home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
EnigmA Amiga Run 1997 July
/
EnigmA AMIGA RUN 20 (1997)(G.R. Edizioni)(IT)[!][issue 1997-07 & 08][EAR-CD IV].iso
/
lightwave
/
tips
/
lwdoc.txt
< prev
next >
Wrap
Internet Message Format
|
1996-12-09
|
50KB
From shf@netcom.com
Date: Fri, 6 Jan 1995 19:13:05 -0800
From: Stuart Ferguson <shf@netcom.com>
Reply to: lwplugin-l@netcom.com
To: lwplugin-l@netcom.com
Subject: LWSDK: flat text docs
LightWave Plug-in Architecture -- Stuart Ferguson 1/6/95
1 Plug-In Interface
1.1 Server Identification
1.2 Server Activation Function
1.3 The Global Function
1.4 Plug-in and Built-in Servers
2 Server Interface
2.1 Plug-in Initialization and Cleanup
(1) Startup usage
(2) Shutdown usage
2.2 Activation Function
(3) Activation function args
(4) ActivateFunc type
2.3 Global Function
(5) GlobalFunc types
(6) GlobalFunc types
2.4 The Global Server Class
(7) Global activation data
2.5 External Function Entry Points
(8) XCALL Definitions
2.6 Single-Service Plug-ins
(9) Activate usage
2.7 Multiple-Service Plug-ins
(10) ServerRecord type
3 Example Plug-in Service
3.1 String Transform Class
(11) Test types
(12) Test types
3.2 String Transform Functions
(13) String transform arguments
3.2.1 Length Operation
(14) Length function body
3.2.2 Reverse Operation
(15) Reverse function body
3.2.3 Capitalization Operation
(16) Capitalize function body
3.2.4 Double Operation
(17) Double function body
3.3 Implementing Servers
3.3.1 Single-Service Plug-in -- Reverse
(18) Test Reverse plug-in program
3.3.2 Multiple-Service Plug-in -- Caps & Double
(19) Test Caps and Double plug-in program
3.3.3 Built-in Server -- Length
(20) Test host utilities
4 Creating a Plug-in
4.1 Amiga -- SAS/C Compiler
(21) Makefile examples
4.2 Amiga -- Manx Compiler
(22) Makefile examples
4.3 Microsoft's Windows
(23) Makefile examples
4.4 SGI Unix
(24) Makefile examples
4.5 Linking with LightWave
(25) Config file examples
(26) Config file examples
1 Plug-In Interface
There are two parts to the system-generic plug-in interface: the host
side and the server side. The host is the application program which
wants to load external code modules to perform some generic type of
operation. Servers are imported routines (which can be loaded
plug-ins or internal built-ins) which implement a specific instance of
a generic type of service.
The host interface provides facilities to create server classes,
register plug-in modules, and perform lazy loading and activation of
registered servers. There is a fairly elaborate name and type mapping
scheme which allows a great deal of flexibility in how modules are
used, but which still provides a fairly simple interface for those who
do not need the full facility.
The server interface provides an easy method to write programs that
will operate as plug-ins. Different classes of plug-in services will
require different host interfaces, but the loading and initialization
part of the server interface is standard and works with the host
portion of the system.
1.1 Server Identification
The plug-in interface is designed to allow the host to have any number
of servers loaded to perform as many different functions as the host
wants to define. The servers in the system are referenced by a
combination of class and name.
A Server Class is a string which determines the type of service which
the server can perform. This might be strings like "TEXTURE" or
"FileRequester". Many servers can have the same class, and all
servers of the same class have the same host interface.
A Server Name is a string which refers to a specific server within a
given class. This might be something like "FractalNoise3D" or
"Default". The name must be unique among the servers of the same
class.
The names for class and server identification should be byte strings
containing characters only the the ASCII range 33-127. By convention
these string contain no spaces and no characters outside 7-bit ASCII.
Case is significant in distinguishing different classes and servers
within classes.
1.2 Server Activation Function
Every server has a single `activation' function. This is the function
which the host calls to access the service provided by the server.
For some servers this one function will perform the whole action and
for others this will only be a prelude to a sequence of actions.
Servers which must remain loaded after they return from their
activation function must be locked by the host while there are actions
pending or they may be unloaded.
1.3 The Global Function
The activation function for every server is called with a `global'
function pointer which provides access to the internal global state of
the host system. The server calls the function with a string
identifing the global data requested and a flag for how it will be
used. The host can service this request, or the request can be passed
on to global plug-in servers.
1.4 Plug-in and Built-in Servers
Servers can be either plug-in or built-in. A plug-in server is
implemented as a file containing code that can be loaded and unloaded
as needed. A built-in server is implemented as a callback within the
host itself. Having both allows the host to provide a standard set of
servers which it handles the same way it handles plug-in servers,
without having to unbundle their functionality in a way that can be
replaced or used by other programs.
2 Server Interface
A plug-in server is written like any ordinary C program, but instead
of a single "main()" entry point, a server has a different primary
entry point and several possible additional entry points. The server
is linked with initializaion code (different from the normal shell or
Workbench init) which places these interfaces where the host can
access them.
There are two main types of plug-in modules: those providing a single
server and those providing multiple servers. It is simple to have one
server per module, but it can be more efficient and useful to define
many servers with a single code file.
All servers require an activation function, and all plug-ins have the
option of providing initialization and cleanup functions. The header
for server types is `splug.h'.
2.1 Plug-in Initialization and Cleanup
In both the single and multiple versions of the plug-in module, there
are optional entry points which allow the module to initialize itself
when it is first loaded and to clean itself up before being unloaded.
If the plug-in code does not contain functions with these names, no
attempt will be made to call them.
The Startup function, if present, will be called when the plug-in is
first loaded into the host system. The return value is global data
for the server which is passed to the Activate and Shutdown entry
points as ` serverData'. A zero return value (null pointer)
indicates failure, so even a plug-in with no data should return
something.
(1) Startup usage
void *
Startup (void)
If provided, the server's Shutdown function is called just before the
server module is unloaded from the host. Any allocated server data
should be freed at this point. Note that even though it is an error,
this function may be called even when the server is locked, so correct
cleanup should be done in this case as well.
(2) Shutdown usage
void
Shutdown (
void *serverData)
2.2 Activation Function
All servers have a single activation function which is the entry point
for the host to get access to the service provided by the server. The
activation function gets passed the version number for the service
implementation, the `global' pointer to access global host data,
class-specific `local' data, and private data maintained by the
plug-in. The version number is application-defined, but typically it
represents the revision of the interface that the host expects the
server to use. Typically a server will not attempt to operate if the
version number is greater than it expects. The ` serverData' is
returned by the Startup entry point in a plug-in. The global function
can be called to get global data from the host enviroment, and the
contents of the `local' pointer are defined by the type of service.
(3) Activation function args
long version,
GlobalFunc *global,
void *local,
void *serverData
The activation function returns an error code if the attempt to call
failed because of some clash between the server and the host
environment. If the server was able to process the request, even it
failed to complete it, it should return AFUNC_OK. If the version
number is not a value which the server can explicitly handle it should
return AFUNC_BADVERSION. If there is some global data the server
cannot get which it requires it should return AFUNC_BADGLOBAL. Severe
problems with the contents of the local data, such as some necessary
pointer in the local data being null, may be reported by returning
AFUNC_BADLOCAL. Any other errors from the server (running out of
memory, bad filenames, user aborts, etc.) must be provided for by the
specific plug-in protocol.
(4) ActivateFunc type
typedef int ActivateFunc (<Activation function args>);
#define AFUNC_OK 0
#define AFUNC_BADVERSION 1
#define AFUNC_BADGLOBAL 2
#define AFUNC_BADLOCAL 3
2.3 Global Function
The global function passed by the host to the server is a special
function which returns the pointer to some global data given by a
string ID. These data blocks will often contain function pointers,
but can be anything.
(5) GlobalFunc types
typedef void * GlobalFunc (const char *, int);
. . .
When a server calls the global function, it passes a string which
identifies the global data required and a code for the way the data
will be used. If the data pointer is not available, null is returned.
The ID's that will be recognized depends on the host, on the available
global plug-ins and perhaps on the server class.
The use code depends on how the result of the call will be used. If
the returned pointer will only be used for the course of the
activation function itself, the TRANSIENT code should be used. If the
data will be used after the activation function returns, such as in a
server that requires locking, the ACQUIRE code should be used. In
this case there must be a matching RELEASE call made when the data
pointer is no longer required. RELEASE calls need only be made for
ACQUIRE calls which returned a non-null pointer. The return value
from a release mode global data call is undefined.
(6) GlobalFunc types
. . .
#define GFUSE_TRANSIENT 0
#define GFUSE_ACQUIRE 1
#define GFUSE_RELEASE 2
2.4 The Global Server Class
The server class given by the name "Global" is special in that it
allows multiple plug-in servers to share common data or routines. In
fact, the members of the Gobal class are extensions to the set of ID
strings that can be passed to the "global" function.
When a server calls the global function with an ID string, the host
can service the request itself or has the option of pass unrecognized
ID's to Global class servers of the same name. For example, if the ID
is "Mambo Functions," the host may recognize this itself and return a
pointer value. If it does not recognize it, it may attempt to
activate a server of class "Global" with name "Mambo Functions." If
such a server exists, it may be locked or unlocked, depending on the
use type of the global request, and it will be called to get the value
of the global pointer for the orignal requester.
The activation function of a Global server is called with a
GlobalService structure which will be initialized with the ID string
for the request. The server must fill in the data pointer with a
value which will be returned to the client, which may be null if the
server wishes to deny the request. The string is passed as data so
that the same activation function may be used for multiple servers.
(7) Global activation data
typedef struct st_GlobalService {
const char *id;
void *data;
} GlobalService;
2.5 External Function Entry Points
Functions in the plug-in get called directly by the host, and this is
a funky thing in some systems since they are different environments.
The XCALL_ and XCALL_INIT macros take care of everything for all
different systems and compilers, so these can be used to make
multi-platform servers from a single source code.
XCALL_ is used on the return type, e.g. XCALL_(int) for an external
entry point returning an int. XCALL_INIT is used as the first
statement of the function. Both must be used for full compatibility,
but XCALL_INIT is only non-null for Manx small-code modules.
(8) XCALL Definitions
<XCALL_ and XCALL_INIT system-specific definition>
The activation function as well as any function pointers returned from
the activation function need the XCALL treatment. Startup and
Shutdown do not.
2.6 Single-Service Plug-ins
A single-service plug-in is a C program with an entry point for the
activation callback and global symbols for the class and name of the
server. There are also optional entry points for initialization and
cleanup.
This plug-in must contain a global character string with the name
`ServerClass'. This string defines the class of this server and the
server will not be loaded if this string does not match the service
type string requested by the host.
It must also contain a global character string called `ServerName'
which holds the name for this specific server.
The activation function must be called Activate, which takes the
arguments as described above.
(9) Activate usage
XCALL_(int)
Activate (<Activation function args>)
2.7 Multiple-Service Plug-ins
A multiple-service plug-in is a C program which defines multiple
servers through a standard set of global symbols. In particular, a
multiple server module must contain a global array with the name
`ServerDesc' composed of elements of the ServerRecord type. The last
record in the array must have a null class name pointer.
(10) ServerRecord type
typedef struct st_ServerRecord {
const char *class;
const char *name;
ActivateFunc *activate;
} ServerRecord;
The plug-in module may also have Startup and Shutdown entry points,
and all the activate functions in the plug-in will get the same
serverData as returned from the Startup function. The assumption is
that the servers all share a module for some logical reason, so the
sharing of global data is not unreasonable.
3 Example Plug-in Service
This describes a hypothetical server class and creates some samples of
plug-in modules using it. This serves as a testbed for third parties
to create test plug-ins, so it should have some general capability.
3.1 String Transform Class
This server class will perform manipulations on character strings,
like reverse them, capitalize them, etc. This class will be
"StringXfrm".
A new server class is completely defined by the semantics of the
activation function for the class. The activation function takes a
pointer argument from the host, `local' which is a reference to data
for the particular service the host needs performed. It also gets a
`global' function pointer which will return global data as needed by
the server.
The `local' pointer will point to a StringLocal structure which holds
the data for the current operation. This is a null-terminated string
and the length of the string buffer, plus a temporary scratch buffer
and its length. The server will overwrite `buf' with the result, and
will set the `overflow' status flag if the buffers were too short.
(11) Test types
typedef struct st_StringLocal {
char *buf;
char *tmpBuf;
int len, tmpLen;
int overflow;
} StringLocal;
. . .
For the string transform class of server, the global function can
return a `progress' function which can be called by the server to give
the user feedback about its progress. This is returned using a string
ID of "Progress Function."
(12) Test types
. . .
typedef void StringProgress (void);
We'll stick these definitions into the `t_plug.h' header file for test
modules to use.
3.2 String Transform Functions
The functions to do string transformations are all the same. They all
get the same arguments as defined by the format of the activation
function. The local pointer is specific to the string transform
class. There is no ` serverData' for any of the transforms since
there is no Startup function.
(13) String transform arguments
long version,
GlobalFunc *global,
StringLocal *local,
void *serverData
String activation functions may return with an error code if the
version number is wrong or if the global progress function is not
available.
3.2.1 Length Operation
The length operation gets the length of the string and prints that
as a number into the string buffer.
(14) Length function body
{
if (version != 1)
return AFUNC_BADVERSION;
if (local->len < 10)
local->overflow = 1;
else
sprintf (local->buf, "%ld", strlen (local->buf));
return AFUNC_OK;
}
3.2.2 Reverse Operation
The reverse operation copies the characters from the main buffer
to the temp buffer in reverse order and then copies them back.
This could use a swap operation to reverse them in place, but this
method demonstrates using the temp buffer and returning an
overflow if the temp buffer is too small. This also calls the
progress function as it swaps.
(15) Reverse function body
{
StringProgress *progress;
int len, i;
if (version != 1)
return AFUNC_BADVERSION;
progress = (*global) ("Progress Function",
GFUSE_TRANSIENT);
if (!progress)
return AFUNC_BADGLOBAL;
len = strlen (local->buf);
if (local->tmpLen <= len) {
local->overflow = 1;
return AFUNC_OK;
}
for (i = 0; i < len; i++) {
local->tmpBuf[i] = local->buf[len - i - 1];
(*progress) ();
}
local->tmpBuf[len] = 0;
strcpy (local->buf, local->tmpBuf);
return AFUNC_OK;
}
3.2.3 Capitalization Operation
This just passes through the string and converts each letter to
uppercase, calling the progress function as it goes.
(16) Capitalize function body
{
StringProgress *progress;
char *c;
if (version != 1)
return AFUNC_BADVERSION;
progress = (*global) ("Progress Function",
GFUSE_TRANSIENT);
if (!progress)
return AFUNC_BADGLOBAL;
for (c = local->buf; *c; c++) {
(*progress) ();
if (*c >= 'a' && *c <= 'z')
*c = *c - 'a' + 'A';
}
return AFUNC_OK;
}
3.2.4 Double Operation
This doubles each character in the string by copying the buffer to
the temp buffer and moving twice as many characters back into the
buffer from there. This will fail if the buffers are not big
enough.
(17) Double function body
{
StringProgress *progress;
int len, i;
if (version != 1)
return AFUNC_BADVERSION;
progress = (*global) ("Progress Function",
GFUSE_TRANSIENT);
if (!progress)
return AFUNC_BADGLOBAL;
len = strlen (local->buf);
if (local->tmpLen < len || local->len - 1 < len * 2) {
local->overflow = 1;
return AFUNC_OK;
}
strcpy (local->tmpBuf, local->buf);
for (i = 0; i < len; i++) {
local->buf[i * 2] = local->tmpBuf[i];
local->buf[i * 2 + 1] = local->tmpBuf[i];
(*progress) ();
}
local->buf[len * 2] = 0;
return AFUNC_OK;
}
3.3 Implementing Servers
A plug-in module is really a wrapper around the activation function,
and can be implemented several ways. They can be single-service
plug-ins, multiple-service plug-ins, or built-in. This test includes
one of each.
3.3.1 Single-Service Plug-in -- Reverse
The reverse operation is implemented as a single-service plug-in,
so there is one global class and server name. The activation
function is called `Activate' (which it must be).
The C program module itself includes the headers for the test
system and server-side plug-ins. The source file is `tp_rev.c'.
(18) Test Reverse plug-in program
#include <splug.h>
#include "t_plug.h"
#include <string.h>
char ServerClass[] = "StringXfrm";
char ServerName[] = "REVERSE";
XCALL_(int)
Activate (<String transform arguments>)
{
XCALL_INIT;
<Reverse function body>
}
3.3.2 Multiple-Service Plug-in -- Caps & Double
The capitalize and double operations are implemented as one
multiple-service plug-in with two servers. The activation
function entry points can have any name and are not exported
symbols. They are associated with their server name in the
exported array of ServerRecords which has the required name
`ServerDesc'. The source file for this is `tp_cpdb.c'.
(19) Test Caps and Double plug-in program
#include <splug.h>
#include "t_plug.h"
#include <string.h>
static XCALL_(int)
Capitalize (<String transform arguments>)
{
XCALL_INIT;
<Capitalize function body>
}
static XCALL_(int)
Double (<String transform arguments>)
{
XCALL_INIT;
<Double function body>
}
const char class[] = "StringXfrm";
ServerRecord ServerDesc[] = {
{ class, "CAPS", Capitalize },
{ class, "DOUBLE", Double },
{ NULL }
};
3.3.3 Built-in Server -- Length
The length operation will be implemented as a built-in. As
result, all we need is a local function entry point of any name in
the host program.
(20) Test host utilities
static int
ActLength (<String transform arguments>)
{
<Length function body>
}
. . .
4 Creating a Plug-in
Methods for creating plug-ins have been developed for each of the
target platforms. Versions of a plug-in can be created for the
different systems from a single source code with different linking
instructions. Each case that follows includes an implicit makefile
rule to create a ".p" plug-in module from an object file. The macro
SLIB stands for the directory where the startup code and server
libraries are located. SINC is the include directory and OTHER_LIBS
would be any other libraries need by the module.
The final section shows how to add your plug-in to the LightWave host.
4.1 Amiga -- SAS/C Compiler
Linking under SAS/C on the Amiga requires replacing the normal startup
code with plug-in startup code, `serv_s.o'. This can be done by using
the "startup" option when using "sc link" or by placing `serv_s.o'
first in the "FROM" list when using slink. Modules must also be
linked with the server library. Object modules should be built
without stack checking.
(21) Makefile examples
.o.p:
sc link $(CFLAGS) startup=$(SLIB)serv_s.o $*.o\
$(SLIB)server.lib $(OTHER_LIBS) pname=$@
. . .
4.2 Amiga -- Manx Compiler
Linking under the Manx compiler on the Amiga requires using the
plug-in startup code `serv_m.o', which must be placed first in the
list of objects passed to ln. The linker will warn about ".begin" and
"_geta4" overriding library symbols, which is correct behavior in this
case. They should also be linked with the "server_m" library to get
the Manx server library.
(22) Makefile examples
. . .
.o.p:
ln -o $@ $(SLIB)serv_m.o $*.o -lserver_m $(OTHER_LIBS)
. . .
4.3 Microsoft's Windows
Plug-in modules under Windows are just DLLs created by linking with
`serv_w.obj' and `server.lib'. There is no need to create a ".lib" or
".exp" file for the DLL, but the ".def" file should contain an export
statement for the function `_InitServer'. A usable default def file
is provided as "serv.def" in the main include directory. There is no
DLL entry point function.
(23) Makefile examples
. . .
.obj.p:
link32 -dll -out:$@ -def:$(SINC)serv.def $*.obj\
$(SLIB)serv_w.obj server.lib $(OTHER_LIBS)
. . .
4.4 SGI Unix
Plug-in modules under IRIX are shared object modules linked with
`serv_u.o' and `libserver.lib'. The DSO should export the
"_mod_descrip" symbol and use "serv_u" as startup code.
(24) Makefile examples
. . .
.o.p:
ld -shared -exported_symbol _mod_descrip -L$(SLIB)\
$(SLIB)serv_u.o $*.o -o $@ -lserver $(OTHER_LIBS)
4.5 Linking with LightWave
LightWave and Modeler read the names of servers from their startup
configuration files. This method is much faster than scanning a
directory path and allows for some user customization of plug-in names
(such as national localization). It does require that the config file
be accurate, since the host will blindly attempt to use servers that
may not exist. This is non-fatal but may be disconcerting to the
user.
For Modeler, for example, the config file on the Amiga is
"MOD-config"; on the SGI is ".lwmrc" and on Windows is "LWM.CFG".
This file can contain any number of lines of the following form:
(25) Config file examples
Plugin <class> <name> <module> <user name>
. . .
Each line describes a single server given by Class and Name. The
module is the plug-in file containing the server and the user name is
the string to display on the interface for describing the server's
function. Class, name and module are delimited by spaces, and the
user name is the rest of the line. Here are some examples (lines wrap
for readability -- each statement has to be a single line).
(26) Config file examples
. . .
Plugin CommandSequence Demo_AllBGLayers layerset.p Include Background
Plugin CommandSequence Demo_NextEmptyLayer layerset.p Next Empty
Plugin MeshDataEdit Demo_MakeSpikey z:lw/plugin/spikey.p Spikey
Subdivide
Plugin ImageLoader PDQ_Targa pdq/targa.lwp Truevision Targa Image
====================================
LightWave Images -- Stuart Ferguson 12/5/94
1 Introduction
(1) Public declarations
(2) Public foreward definitions
2 Image I/O Server Interface
2.1 Image Loaders
(3) Public types
2.2 Image Savers
(4) Public types
2.3 Result Value
(5) Public declarations
2.4 Image Transfer Protocols
2.4.1 Color Protocol
(6) Public types
2.4.2 Index Protocol
(7) Public types
2.4.3 Generic Protocol
(8) Public types
(9) Public foreward definitions
2.4.4 Error Handling
2.4.5 Misc Types
(10) Public declarations
(11) Public declarations
2.5 Monitor Objects
(12) Monitor types
(13) Monitor declarations
3 Test Server
3.1 Targa Reader
(14) Targa types
(15) Targa functions
(16) Targa utilities
(17) Read targa data
(18) Read targa lines
(19) Read uncompressed targa line
(20) Read compressed targa line
(21) Read a targa pixel element into `bgra'
(22) Store `bgra' pixel to line buffers
3.2 Targa Saver
(23) Targa types
(24) Targa functions
(25) Targa utilities
(26) Targa utilities
(27) Targa utilities
3.3 Plugin Module
(28) Targa Image server
1 Introduction
This module provides interfaces for dealing with image types commonly
employed by LightWave users. This allows the loading and saving of
large, deep images in an expandable set of formats, and for accessing
the data in a uniform manner regardless of the underlying data format.
This interface is designed with plug-in image loaders and savers in
mind and it provides some built-in IFF format support.
Image types are given by the following values. RGB24 is an image with
eight bits each of red, green and blue data for each pixel. GREY8 is
an image with eight bits of greyscale value at each pixel. INDEX8 is
an image with up to eight bits of color index at each pixel, mapped
through a 24 bit color table.
(1) Public declarations
#define IMG_RGB24 0
#define IMG_GREY8 1
#define IMG_INDEX8 2
. . .
Image color component, grey or index values are all unsigned chars
scaled from 0 to 255.
(2) Public foreward definitions
typedef unsigned char ImageValue;
. . .
2 Image I/O Server Interface
The image input and output interfaces are designed to be extended with
plug-in loaders and savers. As result, each interface really only
defines the local data structure for the activation function.
2.1 Image Loaders
Image loaders are servers that are called sequentially until one is
able to load the image file. An application will normally have a
standard format in which images are saved, so that will normally be
tried first after which other loaders may be tried in any order the
host can determine. If loaders are just scanned in the host plug-in
database they will be called in something like alphabetical order.
The activation call for a loader gets passed a pointer to a filename
as well as callbacks for image data transfer. If the loader cannot
open the file it sets the `result' field to IPSTAT_BADFILE and
returns. If it does not recognize the file format, it sets the result
to IPSTAT_NOREC. If it can load the image, it calls the `begin'
callback with type of image protocol it would like. The loader then
sends the data from the file to the host through the protocol and
calls the `done' callback when complete to allow the source to dispose
of the protocol. These callbacks are called with the `priv_data'
pointer as the first field.
(3) Public types
typedef struct st_ImLoaderLocal {
void *priv_data;
int result;
const char *filename;
Monitor *monitor;
ImageProtocolID (*begin) (void *, int type);
void (*done) (void *, ImageProtocolID);
} ImLoaderLocal;
. . .
2.2 Image Savers
Image savers are servers of "ImageSaver" class that write an image out
to a file in a single specific format. The save format is typically
chosen directly by the user with an interface showing the user names
for the servers, so no scanning or ordering is required.
The activation call for savers gets a filename, a requested protocol
type, and a callback for the host to output its image data to the
saver protocol. The flag in the `sendData' callback can contain the
IMGF_ALPHA bit if the saver can store alpha data and IMGF_REVERSE bit
if the saver wants the data sent bottom to top rather than top to
bottom. The saver should create a protocol and set flags most
appropriate for the destination file format. The `sendData' callback
will return a non-zero error code if anything failed on the sending
end or if the destination reports an error.
(4) Public types
. . .
typedef struct st_ImSaverLocal {
void *priv_data;
int result;
int type;
const char *filename;
Monitor *monitor;
int (*sendData) (void *, ImageProtocolID, int);
} ImSaverLocal;
. . .
2.3 Result Value
The result value indicates the status of the loader or saver upon
completion. If the load or save was sucessful, the value should be
IPSTAT_OK. If a loader fails to recognize a file as something it can
load it should set the result to IPSTAT_NOREC. If the server could
not open the file it should return IPSTAT_BADFILE. Any other error is
just a generic failure of the loader or saver and so should set the
result to IPSTAT_FAILED. Other failure modes might be possible if
required in the future.
(5) Public declarations
. . .
#define IPSTAT_OK 0
#define IPSTAT_NOREC 1
#define IPSTAT_BADFILE 2
#define IPSTAT_ABORT 3
#define IPSTAT_FAILED 99
. . .
2.4 Image Transfer Protocols
Images are passed from source to destination using an image protocol.
Typically, the source will select the protocol type and the
destination will create a protocol of that type. The source will then
send the image data to the source by calling callbacks in the
protocol. Both ends are then given an opportunity to clean up. This
is called a pusher protocol since the source "pushes" the data at the
destination rather than the destination pulling it.
There are two protocols for the three types of images: color and index
protocols. The protocol `type' can have any of the same values as
image type and determines the callbacks in the protocol and what they
do. Protocols contain a private data pointer which should be passed
as the first argument to all the callbacks.
2.4.1 Color Protocol
The color protocol is used for the RGB and grey valued images
(RGB24 and GREY8 types). The source starts the output by calling
the `setSize' function with the width and height of the image and
flags. The flags can contain the IMGF_ALPHA bit to indicate that
the source data contains an alpha channel. The source then sends
the data by calling the `sendLine' function with each image row
number and a pointer to a line of image data and a line of alpha
data, if any was indicated. For greyscale images, the image line
consists of one image value per column in the image (G1 G2 ...
Gw). For RGB images, this line data consists of three image
values per column of the image in RGB order (R1 G1 B1 R2 G2 B2 ...
Rw Gw Bw). The alpha data is in greyscale format.
(6) Public types
. . .
typedef struct st_ColorProtocol {
int type;
void *priv_data;
void (*setSize) (void *, int, int, int);
int (*sendLine) (void *, int, const
ImageValue *,
const ImageValue *);
int (*done) (void *, int);
} ColorProtocol;
. . .
2.4.2 Index Protocol
Colormap index images use the index protocol. The source must
first call `setSize' and `numColors' with image size, flags and
number of entries in the colormap. The source must then set the
colormap by calling the `setMap' callback for each entry in the
colormap. Any entry which is not set is left undefined. The data
in the image is then filled in using the `sendLine' function just
like the greyscale case except that the image values are not grey
values but colormap indices. Alpha values are in greyscale data
format.
(7) Public types
. . .
typedef struct st_IndexProtocol {
int type;
void *priv_data;
void (*setSize) (void *, int, int, int);
void (*numColors) (void *, int);
void (*setMap) (void *, int, ImageValue[3]);
int (*sendLine) (void *, int, const
ImageValue *,
const ImageValue *);
int (*done) (void *, int);
} IndexProtocol;
. . .
2.4.3 Generic Protocol
The generic protocol is either of these possibilities plus the
type field for easy type identifcation.
(8) Public types
. . .
typedef union un_ImageProtocol {
int type;
ColorProtocol color;
IndexProtocol index;
} ImageProtocol;
(9) Public foreward definitions
. . .
typedef union un_ImageProtocol *ImageProtocolID;
2.4.4 Error Handling
There are two specific mechanisms for dealing with errors that
occur while using image protocols. The destination can return
error codes from the `sendLine' and `done' callbacks, and the
source can pass an error code to the destination's `done'
callback.
If an error occurs in the source of a protocol, such as a failure
partway though reading a file, the source can stop calling
`sendLine' prematurely. This will often trigger an error in the
destination since it will have been keeping track of the amount of
data sent. The source should then also pass a non-zero error code
to the `done' callback which will signal an error to the
destination.
If an error occurs in the destination of a protocol, such as a
failure partway through saving an image, the destination should
start to return a non-zero error code from `sendLine.' A
well-written source will stop sending data when this happens, but
the destination should be prepared to continue to get lines of
data and to continue to return an error code. A failed
destination should also return a non-zero error code from the
`done' callback.
2.4.5 Misc Types
Flags to be passed to `setSize' and `sendData' callbacks.
(10) Public declarations
. . .
#define IMGF_ALPHA 1
#define IMGF_REVERSE 2
. . .
There are also some protocol macros defined to get the whole
calling interface right.
(11) Public declarations
. . .
#define IP_SETSIZE(p,w,h,f) (*(p)->setSize)
((p)->priv_data,w,h,f)
#define IP_NUMCOLORS(p,n) (*(p)->numColors)
((p)->priv_data,n)
#define IP_SETMAP(p,i,val) (*(p)->setMap)
((p)->priv_data,i,val)
#define IP_SENDLINE(p,ln,d,a) (*(p)->sendLine)
((p)->priv_data,ln,d,a)
#define IP_DONE(p,err) (*(p)->done)
((p)->priv_data,err)
2.5 Monitor Objects
Monitors are simple data structures defining an interface which the
server can use to give feedback to the host on its progress in
performing some task. They are introduced here for file loading and
saving feedback, but are used in a more general context so they are a
good thing to be familiar with.
A Monitor consists of some generic data and three functions: init,
step and done. The `init' function is called first with the number of
steps in the process to be monitored, which is computed and passed up
from the server being monitored. As the task is processed, the `step'
function is called with the number of steps just completed (often
one). These step increments should eventually add up to the total
number and then the `done' function is called, but `done' may be
called early if there was a problem or the process was aborted. The
`step' function will return one if the user requested an abort and
zero otherwise.
(12) Monitor types
typedef struct st_Monitor {
void *data;
void (*init) (void *, unsigned int);
int (*step) (void *, unsigned int);
void (*done) (void *);
} Monitor;
The server is masked from any errors in the monitor that may occur on
the host side of the interface. If there is a problem with putting up
a monitor, the functions should still return normally, since the
monitor is for user feedback and is not that critical.
There are some macros provided to call a monitor which will do the
right thing if the monitor pointer is null, which means no monitoring
is necessary. MON_INCR is used for step sizes greater than one and
MON_STEP is used for step sizes exactly one.
(13) Monitor declarations
#define MON_INIT(mon,count) if (mon) (*mon->init) (mon->data,
count)
#define MON_INCR(mon,d) (mon ? (*mon->step) (mon->data, d) :
0)
#define MON_STEP(mon) MON_INCR (mon, 1)
#define MON_DONE(mon) if (mon) (*mon->done) (mon->data)
3 Test Server
This is a very simple server designed to test an alternate image
format. The single plugin will load and save Targa 32 and 24 bit
formats.
3.1 Targa Reader
The targa loader will recognize a targa file by reading the header
into this data struct. The `type' gives the compression format and
interpretation of image data, `bits' gives the pixel size and
`reverse' indicates if the lines will come bottom to top.
(14) Targa types
typedef struct st_TargaInfo {
unsigned char type, bits;
short width, height;
int reverse;
} TargaInfo;
#define CKPT_TGA_BADFILE 991
#define CKPT_TGA_NOREC 992
. . .
The main reader just reads the header, and if this can be matched as a
targa image it reads the body. Errors will be captured by the
exception context and will set the result code.
(15) Targa functions
int
TargaLoader (
long version,
GlobalFunc *global,
ImLoaderLocal *local,
void *servData)
{
ReadStrmID strm;
TargaInfo tga;
int fail;
if (version != 1)
return AFUNC_BADVERSION;
if (!CkptCapture (fail)) {
if (fail == CKPT_ABORT)
local->result = IPSTAT_ABORT;
else if (fail == CKPT_TGA_BADFILE)
local->result = IPSTAT_BADFILE;
else if (fail == CKPT_TGA_NOREC)
local->result = IPSTAT_NOREC;
else
local->result = IPSTAT_FAILED;
return AFUNC_OK;
}
strm = StrmReadOpen (local->filename, NULL);
if (!strm)
CkptRecover (CKPT_TGA_BADFILE);
ARM1 (StrmReadClose, strm);
if (!ReadTargaHeader (strm, &tga))
CkptRecover (CKPT_TGA_NOREC);
<Read targa data>
StrmReadClose (strm);
CkptEnd ();
local->result = IPSTAT_OK;
return AFUNC_OK;
}
. . .
The reader will check just a very few things in the header before it
decides it can load the file. This might be a problem since targa
files are much less self-identifying than others. Any values that are
out of range cause this to return 0, indicating failure to recognize.
If it returns 1, the info has been read.
(16) Targa utilities
static int
ReadTargaHeader (
ReadStrmID strm,
TargaInfo *tga)
{
unsigned char byte, idLen;
(*strm->readBytes) (strm, &idLen, 1);
(*strm->readBytes) (strm, &byte, 1);
if (byte)
return 0;
(*strm->readBytes) (strm, &tga->type, 1);
if (tga->type != 2 && tga->type != 10)
return 0;
(*strm->skipBytes) (strm, 9);
(*strm->readIWords) (strm, &tga->width, 1);
(*strm->readIWords) (strm, &tga->height, 1);
(*strm->readBytes) (strm, &tga->bits, 1);
if (tga->bits != 24 && tga->bits != 32)
return 0;
(*strm->readBytes) (strm, &byte, 1);
byte &= 0xF0;
if (byte == 0)
tga->reverse = 1;
else if (byte == 0x20)
tga->reverse = 0;
else
return 0;
(*strm->skipBytes) (strm, idLen);
return 1;
}
. . .
We will always send the data in RGB24 format, since we currently only
recognize targa 32 and 24 bit formats. Buffers are allocated to
transfer the rgb and alpha data in the right byte-packing order. The
protocol is started and recovery actions are armed in case we fail
partway through.
(17) Read targa data
{
ImageProtocolID ip;
ColorProtocol *cp;
ImageValue *buf, *abuf;
int bufSize, i, alpha;
ip = (*local->begin) (local->priv_data, IMG_RGB24);
if (!ip)
CkptRecover (CKPT_IO_ERROR);
alpha = (tga.bits == 32);
bufSize = tga.width * 4;
buf = NEW_Z (bufSize);
ARM_Z (buf, bufSize);
abuf = (alpha ? buf + 3 * tga.width : NULL);
cp = &ip->color;
IP_SETSIZE (cp, tga.width, tga.height, (alpha ? IMGF_ALPHA
: 0));
ARM2 (local->done, local->priv_data, cp);
MON_INIT (local->monitor, tga.height);
if (local->monitor)
ARM1 (local->monitor->done, local->monitor->data);
if (CkptBegin ()) {
ARM2 ((void*)cp->done, cp->priv_data, -1);
<Read targa lines>
CkptEnd ();
}
if (IP_DONE (cp, 0))
CkptRecover (CKPT_IO_ERROR);
MON_DONE (local->monitor);
(*local->done) (local->priv_data, ip);
FREE_Z (buf, bufSize);
}
Basically we just read all the lines in forward or reverse order.
They may be compressed or not.
(18) Read targa lines
for (i = 0; i < tga.height; i++) {
int ln, x;
unsigned char bgra[4];
ImageValue *rgbBuf, *alphaBuf;
rgbBuf = buf;
alphaBuf = abuf;
if (tga.type == 2) {
<Read uncompressed targa line>
} else {
<Read compressed targa line>
}
ln = (tga.reverse ? tga.height - i - 1: i);
if (IP_SENDLINE (cp, ln, buf, abuf))
break;
if (MON_STEP (local->monitor))
CkptRecover (CKPT_ABORT);
}
Uncompressed lines of data are just `width' pixels which we read
sequentially.
(19) Read uncompressed targa line
for (x = 0; x < tga.width; x++) {
<Read a targa pixel element into `bgra'>
<Store `bgra' pixel to line buffers>
}
A compressed line is enough pixels in literals and runs to fill a
scanline. If the scanline is not exactly filled, this is an error.
(20) Read compressed targa line
x = 0;
while (x < tga.width) {
unsigned char test;
int count, k;
(*strm->readBytes) (strm, &test, 1);
count = (test & 0x7F) + 1;
if (test & 0x80) {
<Read a targa pixel element into `bgra'>
for (k = 0; k < count; k++) {
<Store `bgra' pixel to line buffers>
}
} else {
for (k = 0; k < count; k++) {
<Read a targa pixel element into `bgra'>
<Store `bgra' pixel to line buffers>
}
}
x += count;
}
if (x != tga.width)
CkptRecover (CKPT_IO_ERROR);
24 and 32 bit targa pixels are just 3 or 4 bytes in BGR(A) order. We
read that into an array that will be unpacked into the format we want.
(21) Read a targa pixel element into `bgra'
(*strm->readBytes) (strm, bgra, (alpha ? 4 : 3));
Once we have read a pixel we can store it to the accumulating output
row by sticking the rgb and optional alpha into their buffers.
(22) Store `bgra' pixel to line buffers
*rgbBuf++ = bgra[2];
*rgbBuf++ = bgra[1];
*rgbBuf++ = bgra[0];
if (alpha)
*alphaBuf++ = bgra[3];
3.2 Targa Saver
(23) Targa types
. . .
typedef struct st_TargaSave {
WriteStrmID strm;
Monitor *mon;
int width, height;
int alpha, result;
} TargaSave;
The targa saver sets up a protocol of the RGB24 type and requests a
send from the source. Since the protocol callbacks have to return
result codes, the ckpt mechanism is more of a hinderance here.
(24) Targa functions
. . .
int
TargaSaver (
long version,
GlobalFunc *global,
ImSaverLocal *local,
void *servData)
{
ImageProtocol prot;
TargaSave tga;
if (version != 1)
return AFUNC_BADVERSION;
if (local->type != IMG_RGB24) {
local->result = IPSTAT_FAILED;
return AFUNC_OK;
}
tga.strm = StrmWriteOpen (local->filename);
if (!tga.strm) {
local->result = IPSTAT_BADFILE;
return AFUNC_OK;
}
tga.result = IPSTAT_OK;
tga.mon = local->monitor;
prot.type = IMG_RGB24;
prot.color.priv_data = &tga;
prot.color.setSize = Targa_SetSize;
prot.color.sendLine = Targa_SendLine;
prot.color.done = Targa_Done;
(*local->sendData) (local->priv_data, &prot, IMGF_ALPHA);
StrmWriteClose (tga.strm);
local->result = tga.result;
return AFUNC_OK;
}
The set size callback will just record the size and alpha status in
the save info and write the header. The header is mostly zero expcpt
for a few bytes with special values and the size as reversed byte
order words.
(25) Targa utilities
. . .
XCALL_(static void)
Targa_SetSize (
TargaSave *tga,
int w,
int h,
int flags)
{
unsigned char hdr[12];
short size[2];
int fail;
if (!CkptCapture (fail)) {
tga->result = IPSTAT_FAILED;
return;
}
tga->width = w;
tga->height = h;
tga->alpha = ((flags & IMGF_ALPHA) != 0);
memset (hdr, 0, 12);
hdr[2] = 2;
(*tga->strm->writeBytes) (tga->strm, hdr, 12);
size[0] = w;
size[1] = h;
(*tga->strm->writeIWords) (tga->strm, size, 2);
hdr[0] = (tga->alpha ? 32 : 24);
hdr[1] = 0x20;
(*tga->strm->writeBytes) (tga->strm, hdr, 2);
if (tga->mon)
MON_INIT (tga->mon, tga->height);
CkptEnd ();
}
. . .
Writing a line is really easy. The pixel loop just unwraps the rgb
and optional alpha data into targa pixel format and writes it. Write
errors will return an error code, but nothing else.
(26) Targa utilities
. . .
static int
Targa_SendLine (
TargaSave *tga,
int line,
const ImageValue *data,
const ImageValue *adata)
{
unsigned char bgra[4];
int i, plen;
int fail;
if (tga->result != IPSTAT_OK)
return -1;
if (!CkptCapture (fail)) {
if (fail == CKPT_ABORT)
tga->result = IPSTAT_ABORT;
else
tga->result = IPSTAT_FAILED;
return -1;
}
plen = (tga->alpha ? 4 : 3);
for (i = 0; i < tga->width; i++) {
bgra[2] = *data++;
bgra[1] = *data++;
bgra[0] = *data++;
if (tga->alpha)
bgra[3] = *adata++;
(*tga->strm->writeBytes) (tga->strm, bgra, plen);
}
if (tga->mon && MON_STEP (tga->mon))
CkptRecover (CKPT_ABORT);
CkptEnd ();
return 0;
}
. . .
The `done' callback completes the monitor transaction and returns the
aggregate error status.
(27) Targa utilities
. . .
static int
Targa_Done (
TargaSave *tga,
int error)
{
if (error)
tga->result = IPSTAT_FAILED;
if (tga->mon)
MON_DONE (tga->mon);
return (tga->result != IPSTAT_OK);
}
3.3 Plugin Module
(28) Targa Image server
#include <image.h>
#include <strmu.h>
#include <splug.h>
#include <std.h>
<Targa types>
<Targa utilities>
<Targa functions>
ServerRecord ServerDesc[] = {
{ "ImageSaver", "Targa", TargaSaver },
{ "ImageLoader", "Targa", TargaLoader },
{ NULL }
};